#include"豆瓣电影数据的知识图谱构建.h"

//需要声明定义的全局变量
adjGraph movieG;//电影图谱
map<string, int> nodeIndexTable;//结点编号索引表

//和关系搜索相关的变量
bool Found = false;//用来区分是否存在关系，默认不存在关系

void Initial_Index_TableAndGrahp_Nodes()
{
	//先打开文件并输入文件
    ifstream inFile("nodes.csv", ios::in);
    if (!inFile)
    {
        cout << "打开文件失败！" << endl;
        exit(1);
    }

    //先扫描一次得到总结点的个数，用来动态开辟结点的空间
    int node_numb = 0;//用来统计结点的个数。
    string line;
    string field;

    //以下为存储顶点具体元素的变量
    string node_name;//结点名称
    int numb;//结点编号
    string node_type;//结点类型
    int color;//结点色号

    //初始化  结点编号查找表
    while (getline(inFile, line))
    {
        istringstream sin(line); //将整行字符串line读入到字符串流sin中

        getline(sin, field, ','); //将字符串流sin中的字符读入到field1字符串中，以逗号为分隔符
        node_name = field.c_str();
        getline(sin, field, ','); //将字符串流sin中的字符读入到field2字符串中，以逗号为分隔符
        numb = atoi(field.c_str());

        nodeIndexTable[node_name] = numb;

        node_numb++; //获取结点个数
    }
    //cout << "共读取了：" << node_numb << "个结点。" << endl;

    //让文件指针重新回到文件头部
    inFile.clear();
    inFile.seekg(0);
    
    //为邻接表进行动态开辟结点数组的空间
    movieG.vNodeList = new VNode[node_numb];
    movieG.nodeNumb = node_numb;

    //初始化图的顶点信息
    while (getline(inFile, line))//getline(inFile, line)表示按行读取CSV文件中的数据
    {
        istringstream sin(line); //将整行字符串line读入到字符串流sin中

        getline(sin, field, ','); //将字符串流sin中的字符读入到field1字符串中，以逗号为分隔符
        node_name = field.c_str();
        //cout << field.c_str() << " ";

        getline(sin, field, ','); //将字符串流sin中的字符读入到field2字符串中，以逗号为分隔符
        numb = atoi(field.c_str());
        //cout << atoi(field.c_str()) << " ";

        getline(sin, field, ','); //将字符串流sin中的字符读入到field字符串中，以逗号为分隔符
        node_type = field.c_str();
        //cout << field.c_str() << " ";  //第三处是结点类型
        
        getline(sin, field); //将字符串流sin中的字符读入到field字符串中，以逗号为分隔符
        color = atoi(field.c_str());
        //cout << atoi(field.c_str()) << endl;//将刚刚读取的字符串转换成int 第c四处为颜色对应的编号从1到4分别代表4种颜色
        

        //初始化结点信息
        movieG.vNodeList[numb].name = node_name;
        movieG.vNodeList[numb].type = node_type;
        movieG.vNodeList[numb].color = color;
        movieG.vNodeList[numb].firstarc = NULL;
    }
    
    //用迭代器输出map
    //map<string, int>::iterator it;
    //for (it = nodeIndexTable.begin(); it != nodeIndexTable.end(); it++)
    //{
    //    cout << it->first << " " << it->second << endl;
    //}
    //cout << nodeIndexTable.size() << endl;
    //cout << "读取数据完成" << endl;

    //测试对图结点信息的输出
    //for (int i = 0; i < movieG.nodeNumb; i++)
    //{
    //    cout << "NO." << i << ": " << movieG.vNodeList[i].name << " " << movieG.vNodeList[i].type << " " << movieG.vNodeList[i].color << endl;
    //}
    inFile.close();//关闭文件
}

void CreateGraph()
{
    //先打开文件并输入文件
    ifstream inFile("relationship.movies.csv", ios::in);
    //ifstream inFile("test1.csv", ios::in);
    if (!inFile)
    {
        cout << "打开文件失败！" << endl;
        exit(1);
    }

    int edge_numb = 0;//用来统计边的个数。
    string line;
    string field;

    //用来表示具体的结点关系的变量
    string node1;
    string node2;
    string relationship;

    while (getline(inFile, line))
    {
        istringstream sin(line); 
        //一行一行来读取三元组
        getline(sin, field, ','); 
        node1 = field.c_str();
        getline(sin, field, ','); 
        node2 = field.c_str();
        getline(sin, field, ','); 
        relationship = field.c_str();

        //cout <<"NO." << edge_numb <<"." << node1 << " " << node2 << " " << relationship << endl;
        //system("pause");

        //按关系建图
        ArcNode* newNode1 = new ArcNode;
        newNode1->ToNode_ID = nodeIndexTable[node2];

        //cout << node2 << " " << nodeIndexTable[node2] << " ";
        //cout << node1 << " " << nodeIndexTable[node1] << endl;
        
        newNode1->relationship = relationship;
        newNode1->nextarc = movieG.vNodeList[ nodeIndexTable[node1] ].firstarc;
        movieG.vNodeList[nodeIndexTable[node1]].firstarc = newNode1;

        //cout << movieG.vNodeList[nodeIndexTable[node1]].name << " " << newNode1->relationship << " " << movieG.vNodeList[newNode1->ToNode_ID].name << " ";
        //system("pause");

        //对称建图
        //要不要采用双项图，要根据具体实现的功能来考虑！
        
        
        ArcNode* newNode2 = new ArcNode;
        newNode2->ToNode_ID = nodeIndexTable[node1];
        newNode2->relationship = relationship;
        newNode2->nextarc = movieG.vNodeList[nodeIndexTable[node2]].firstarc;
        movieG.vNodeList[nodeIndexTable[node2]].firstarc = newNode2;
        

        edge_numb++;
    }
    movieG.edgeNumb = edge_numb;

    inFile.close();//关闭文件
}


void OutputGraphy()
{
    for (int i = 0; i < movieG.nodeNumb; i++)
    {
        ArcNode* pMove = movieG.vNodeList[i].firstarc;
        cout << "NO." << i << ": " << movieG.vNodeList[i].name << ":" << endl;
        while (pMove != NULL)
        {
            cout << "       --->" << pMove->relationship << "    --->" << movieG.vNodeList[pMove->ToNode_ID].name <<endl;
            pMove = pMove->nextarc;
        }
        //system("pause");
    }
}


void EasySearchBaseOnPerson(string search_information)//基于人的作品查询
{
    if (nodeIndexTable.count(search_information) == 0)//没找到对应的名称的话
    {
        cout << "查询不到你查询的演员或者导演~" << endl;
    }
    else if (movieG.vNodeList[nodeIndexTable[search_information]].type == "导演结点")
    {
        cout << search_information << "导演的电影有：  " << endl;
        ArcNode* pMove = movieG.vNodeList[nodeIndexTable[search_information]].firstarc;
        while (pMove != NULL)
        {
            if (pMove->relationship == "导演")
            {
                cout << "    --->" << movieG.vNodeList[pMove->ToNode_ID].name << endl;
            }
            pMove = pMove->nextarc;
        }
    }
    else if(movieG.vNodeList[nodeIndexTable[search_information]].type == "演员结点")
    {
        cout << search_information << "出演的电影有：   " << endl;
        ArcNode* pMove = movieG.vNodeList[nodeIndexTable[search_information]].firstarc;
        while (pMove != NULL)
        {
            if (pMove->relationship == "出演")
            {
                cout << "    --->" << movieG.vNodeList[pMove->ToNode_ID].name << endl;
            }
            pMove = pMove->nextarc;
        }
    }
}


void intelligentQandA(string node,string relationship)//智能问答
{
    ArcNode* pMove = movieG.vNodeList[nodeIndexTable[node]].firstarc;
    bool noFound = true;//用来标记查找不到的情况
    cout << node << ": " << endl;
    while (pMove != NULL)
    {
        if (pMove->relationship == relationship)
        {
            cout << "    --->" << relationship << "  --->  " << movieG.vNodeList[pMove->ToNode_ID].name << endl;
            noFound = false;
        }
        pMove = pMove->nextarc;
    }
    if (noFound)
    {
        cout << "没有查找到您所指定的关系~" << endl;
    }
}

void SearchRaletionship(string object1, string object2)//关系查找
{
    bfsFindPath(object1, object2);
    //对于不存在关系的情况的输出
    if (Found == false)
    {
        cout << "\n     这两个对象并不存在关系~" << endl;
    }
}

void bfsFindPath(string u,string v)
{
    //初始化visited数组
    int* visited;//BFS的访问标记数组;
    visited = new int[movieG.nodeNumb];
    for (int i = 0; i < movieG.nodeNumb; i++)
    {
        visited[i] = 0;
    }

    //初始化队列
    Queue* que = new Queue[movieG.nodeNumb];//求最短路径只需要开结点个数大小就行
    //Queue* que = new Queue[10000000]; //求全部路径用的队列的空间要尽可能大
    //这种求最短路径的算法更适合最短路径存在的
    //如果要收缩的范围很大的话，他对队列长度的要求非常大，因为他几乎是每有一条路径就要存一条路径上的所有结点到队列上。

    ArcNode* pMove;
    int rear = -1;//尾指针
    int front = -1;
    int w, i;
    Queue top_info;

    rear++;
    que[rear].ID = nodeIndexTable[u];
    que[rear].parent = -1;// parent为-1表示这是最后一个结点了。
    que[rear].relationship = "\n关系路径如下：\n"; //借用首结点的关系域来控制输出格式。
    visited[que[rear].ID] = 1;//表示结点已被访问。

    while (front != rear)
    {
        front++;//出队一个元素
        w = que[front].ID;

        //找到了对应的编号
        if (w == nodeIndexTable[v]) 
        {
            Found = true;//标记存在关系
            i = front;
            stack<Queue> path_stack;//借用栈来实现正序的输出

            while (que[i].parent != -1)
            {
                path_stack.push(que[i]);
                i = que[i].parent;
            }
            path_stack.push(que[i]);

            //输出关系路径：
            while (!path_stack.empty())
            {
                top_info = path_stack.top();
                path_stack.pop();
                
                if (top_info.relationship != "\n关系路径如下：\n")
                {
                    cout << " ---> " << top_info.relationship << " ---> ";
                }
                else
                {
                    cout << top_info.relationship;
                }
                cout << movieG.vNodeList[top_info.ID].name ;
            }
            return;   //可注释点1
            //这里的退出很重要，这里的退出保证了得到的路径是最短的路径，也即是说，找到了最短的路径之后就立刻退出！
        }

        //载入信息
        pMove = movieG.vNodeList[w].firstarc;
        while (pMove != NULL)
        {
            if (visited[pMove->ToNode_ID] == 0)
            {
                visited[pMove->ToNode_ID] = 1;//如果把这句话注释掉，并且把上面的return注释掉，
                //那就可以通过广度遍历得到全部路径，当然前提是队列的空间足够
                rear++;
                que[rear].ID = pMove->ToNode_ID;
                que[rear].parent = front;
                que[rear].relationship = pMove->relationship;
            }
            pMove = pMove->nextarc;
        }
    }
}


void movieRecommend(string basedMovie)//电影推荐算法
//简单版，基于直接关系的推荐,不考虑基于间接关系的推荐。
{
    int i,j;
    int* path_record;//初值为0
    path_record = new int[movieG.nodeNumb];

    //将路径列表全部初始化为0
    for ( i = 0; i < movieG.nodeNumb; i++)
    {
        path_record[i] = 0;
    }

    //获取推荐基于的顶点所在的链
    ArcNode* pMove = movieG.vNodeList[nodeIndexTable[basedMovie]].firstarc;
    while (pMove != NULL)//遍历 推荐原点链
    {
        ArcNode* pSearch = movieG.vNodeList[pMove->ToNode_ID].firstarc;
        while (pSearch != NULL)//遍历与当前电影结点有关系的边
        {
            if (movieG.vNodeList[pSearch->ToNode_ID].type == "电影结点")
            {
                path_record[pSearch->ToNode_ID]++;
            }
            pSearch = pSearch->nextarc;
        }
        pMove = pMove->nextarc;
    }

    path_record[nodeIndexTable[basedMovie]] = -100;
    //要把本部电影本身去除掉。

    //基于路径数取当前路径最多的前十个电影。
    RecommendNode recommendList[10];//存取前10部推荐电影的id
    int recommend_id;
    for (i = 0; i < 10; i++)
    {
        int max = -1;
        for (j = 0; j < movieG.nodeNumb; j++)
        {
            if (path_record[j] > max)
            {
                max = path_record[j];
                recommend_id = j;
            }
        }
        //得到一个路径后，要把取出的路径数最小化，方便取次大数。
        path_record[recommend_id] = -100;
        recommendList[i].recommendID = recommend_id;
        recommendList[i].pathNumb = max;
    }

    map<string, int> recommendMap;

    //输出对应推荐列表
    cout << "基于电影" << basedMovie << "推荐以下10部电影：" << endl;
    for (i = 0; i < 10; i++)
    {
        //初始化查找表
        recommendMap[movieG.vNodeList[recommendList[i].recommendID].name] = recommendList[i].recommendID;

        cout << "NO." << i + 1 << " :  \t" << movieG.vNodeList[recommendList[i].recommendID].name 
           << "\t" << "含有的路径条数有" << recommendList[i].pathNumb << endl;
    }

    
    system("pause");

    //实现可以指定某一部推荐的电影来查询推荐的原因
    string viewMovie;
    cout << "\n请输入一部以上的你想要查找推荐原因的电影: " << endl;
    cin >> viewMovie;

    if (recommendMap.count(viewMovie)) //要求输入的电影是推荐电影中的一部;
    {
        cout << "推荐" << viewMovie << "的原因如下： \n" << endl;

        ArcNode* pMove = movieG.vNodeList[nodeIndexTable[basedMovie]].firstarc;
        while (pMove != NULL)//遍历 推荐原点链
        {
            ArcNode* pSearch = movieG.vNodeList[pMove->ToNode_ID].firstarc;
            while (pSearch != NULL)//遍历与当前电影结点有关系的边
            {
                if (movieG.vNodeList[pSearch->ToNode_ID].name == viewMovie)
                {
                    cout << basedMovie << " -->" << pSearch->relationship << " -->" << movieG.vNodeList[pMove->ToNode_ID].name
                        << "<-- " << pSearch->relationship << "<-- " << viewMovie << endl;
                    if (pSearch->relationship == "导演")
                    {
                        cout << "因为这两部电影都是" << movieG.vNodeList[pMove->ToNode_ID].name  << "导演的电影。\n"<< endl;
                    }
                    else if (pSearch->relationship == "出演")
                    {
                        cout << "因为这两部电影都有" << movieG.vNodeList[pMove->ToNode_ID].name << "出演。\n" << endl;
                    }
                    else if (pSearch->relationship == "属于")
                    {
                        cout << "因为这两部电影都属于" << movieG.vNodeList[pMove->ToNode_ID].name << "类型的电影。\n" << endl;
                    }
                }
                pSearch = pSearch->nextarc;
            }
            pMove = pMove->nextarc;
        }
    }
    else
    {
        cout << "请输入以上有出现过的电影的名称~" << endl;
    }

}